#nullable disable

using System.Collections;
using System.Runtime.CompilerServices;

namespace Jint.Collections;

internal class HybridDictionary<TValue> : IEnumerable<KeyValuePair<Key, TValue>>
{
    private const int CutoverPoint = 9;
    private const int InitialDictionarySize = 13;
    private const int FixedSizeCutoverPoint = 6;

    private readonly bool _checkExistingKeys;
    private ListDictionary<TValue> _list;
    internal StringDictionarySlim<TValue> _dictionary;

    public HybridDictionary() : this(0, checkExistingKeys: true)
    {
    }

    public HybridDictionary(int initialSize, bool checkExistingKeys)
    {
        _checkExistingKeys = checkExistingKeys;
        if (initialSize >= FixedSizeCutoverPoint)
        {
            _dictionary = new StringDictionarySlim<TValue>(initialSize);
        }
    }

    protected HybridDictionary(StringDictionarySlim<TValue> dictionary)
    {
        _checkExistingKeys = true;
        _dictionary = dictionary;
    }

    public TValue this[Key key]
    {
        get
        {
            TryGetValue(key, out var value);
            return value;
        }
        set
        {
            if (_dictionary != null)
            {
                _dictionary[key] = value;
            }
            else if (_list != null)
            {
                if (_list.Count >= CutoverPoint - 1)
                {
                    SwitchToDictionary(key, value, tryAdd: false);
                }
                else
                {
                    _list[key] = value;
                }
            }
            else
            {
                _list = new ListDictionary<TValue>(key, value, _checkExistingKeys);
            }
        }
    }

    public bool TryGetValue(Key key, out TValue value)
    {
        if (_dictionary != null)
        {
            return _dictionary.TryGetValue(key, out value);
        }

        if (_list != null)
        {
            return _list.TryGetValue(key, out value);
        }

        value = default;
        return false;
    }

    public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updater, TState state)
    {
        if (_dictionary != null)
        {
            _dictionary.SetOrUpdateValue(key, updater, state);
        }
        else if (_list != null)
        {
            _list.SetOrUpdateValue(key, updater, state);
        }
        else
        {
            _list = new ListDictionary<TValue>(key, updater(default, state), _checkExistingKeys);
        }
    }

    private bool SwitchToDictionary(Key key, TValue value, bool tryAdd)
    {
        var dictionary = new StringDictionarySlim<TValue>(InitialDictionarySize);
        foreach (var pair in _list)
        {
            dictionary[pair.Key] = pair.Value;
        }

        bool result;
        if (tryAdd)
        {
            result = dictionary.TryAdd(key, value);
        }
        else
        {
            dictionary[key] = value;
            result = true;
        }
        _dictionary = dictionary;
        _list = null;
        return result;
    }

    public int Count
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get => _dictionary?.Count ?? _list?.Count ?? 0;
    }

    public bool TryAdd(Key key, TValue value)
    {
        if (_dictionary != null)
        {
            return _dictionary.TryAdd(key, value);
        }
        else
        {
            _list ??= new ListDictionary<TValue>(key, value, _checkExistingKeys);

            if (_list.Count + 1 >= CutoverPoint)
            {
                return SwitchToDictionary(key, value, tryAdd: true);
            }
            else
            {
                return _list.Add(key, value, tryAdd: true);
            }
        }
    }

    public void Add(Key key, TValue value)
    {
        if (_dictionary != null)
        {
            _dictionary.GetOrAddValueRef(key) = value;
        }
        else
        {
            if (_list == null)
            {
                _list = new ListDictionary<TValue>(key, value, _checkExistingKeys);
            }
            else
            {
                if (_list.Count + 1 >= CutoverPoint)
                {
                    SwitchToDictionary(key, value, tryAdd: false);
                }
                else
                {
                    _list.Add(key, value);
                }
            }
        }
    }

    public void Clear()
    {
        _dictionary?.Clear();
        _list?.Clear();
    }

    public bool ContainsKey(Key key)
    {
        if (_dictionary != null)
        {
            return _dictionary.ContainsKey(key);
        }

        if (_list != null)
        {
            return _list.ContainsKey(key);
        }

        return false;
    }

    IEnumerator<KeyValuePair<Key, TValue>> IEnumerable<KeyValuePair<Key, TValue>>.GetEnumerator()
    {
        if (_dictionary != null)
        {
            return _dictionary.GetEnumerator();
        }

        if (_list != null)
        {
            return _list.GetEnumerator();
        }

        return System.Linq.Enumerable.Empty<KeyValuePair<Key, TValue>>().GetEnumerator();

    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        if (_dictionary != null)
        {
            return _dictionary.GetEnumerator();
        }

        if (_list != null)
        {
            return _list.GetEnumerator();
        }

        return System.Linq.Enumerable.Empty<KeyValuePair<Key, TValue>>().GetEnumerator();
    }

    public bool Remove(Key key)
    {
        if (_dictionary != null)
        {
            return _dictionary.Remove(key);
        }

        return _list != null && _list.Remove(key);
    }

    /// <summary>
    /// Optimization when no need to check for existing items.
    /// </summary>
    public bool CheckExistingKeys
    {
        set
        {
            if (_list != null)
            {
                _list.CheckExistingKeys = value;
            }
        }
    }
}
